package com.bjy.qa.service.functionaltest.impl;

import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.bjy.qa.dao.functionaltest.*;
import com.bjy.qa.entity.functionaltest.*;
import com.bjy.qa.enumtype.*;
import com.bjy.qa.exception.MyException;
import com.bjy.qa.service.functionaltest.*;
import com.bjy.qa.service.tools.ITaskManagerService;
import com.bjy.qa.util.TimeUtil;
import com.bjy.qa.util.fileexport.LocustExport;
import com.bjy.qa.util.fileparser.CurlParser;
import com.bjy.qa.util.fileparser.OpenApiParser;
import com.bjy.qa.util.fileparser.PostmanParser;
import com.smartentities.json.generator.GeneratorConfig;
import com.smartentities.json.generator.JsonGenerator;
import org.apache.commons.lang.StringUtils;
import org.everit.json.schema.Schema;
import org.everit.json.schema.loader.SchemaLoader;
import org.json.JSONObject;
import org.json.JSONTokener;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.io.FileNotFoundException;
import java.util.*;

@Service
public class ApiService implements IApiService {
    private final Logger logger = LoggerFactory.getLogger(this.getClass());

    @Resource
    ICatalogService iCatalogService;

    @Resource
    ApiDao apiDao;

    @Resource
    StepDao stepDao;

    @Resource
    ApiParamDao apiParamDao;

    @Resource
    IApiParamService iApiParamService;

    @Resource
    IApiHistoryService iApiHistoryService;

    @Resource
    ITaskManagerService iTaskManagerService;

    @Resource
    IAgentService iAgentService;

    @Resource
    AgentDao agentDao;

    @Resource
    ApiLogDao apiLogDao;

    @Resource
    ITestSuiteService iTestSuiteService;

    @Override
    public List<Api> selectAll() {
        List<Api> list = apiDao.selectAll();
        return list;
    }

    @Override
    public Api selectById(int type, Long id) {
        return apiDao.selectById(type, id);
    }

    @Override
    public Api add(Api api, String userName) {
        api.setDesigner(userName);
        apiDao.insert(api); // 保存 api

        // 批量保存 ApiParam
        List<ApiParam> apiParams = api.getApiParams();
        if (apiParams != null) {
            for (ApiParam apiParam : apiParams) {
                // 生成示例数据
                if (api.getType() != CatalogType.INTERFACE_SSH && apiParam.getKind() != ParamKind.KIND_MOCK) {
                    try {
                        JSONObject jsonSchema = new JSONObject(new JSONTokener(apiParam.getDes()));
                        Schema schema = SchemaLoader.load(jsonSchema);
                        GeneratorConfig generatorConfig = GeneratorConfig.fromJsonSchema(schema);
                        JsonGenerator jsonGenerator = new JsonGenerator(generatorConfig);
                        apiParam.setExample(jsonGenerator.generate());
                    } catch (FileNotFoundException e) {
                    }
                }

                apiParam.setApiId(api.getId());
                if (apiParam.getSort() == null) {
                    apiParam.setSort(9999);
                }
                if (StringUtils.isEmpty(apiParam.getName())) {
                    throw new MyException("新增失败，字段名值必填，请检查修改完成后重新保存!");
                }
            }
            iApiParamService.saveBatch(apiParams);
        }

        iApiHistoryService.add(api, userName); // 添加历史记录

        return api;
    }

    @Override
    public Api update(Api api, String userName) {
        // 更新数据需要清除 UpdatedTime 和 CreatedTime，否则不会自动更新修改时间
        api.setDesigner(userName);
        api.setUpdatedAt(null);
        api.setCreatedAt(null);
        apiDao.updateById(api);

        Map<String, Object> paramMap = new HashMap<>(); // 保存有些 api 更新需要修改 step 的内容

        List<ApiParam> newApiParams = new ArrayList<>(); // 需要新增的 ApiParams
        List<ApiParam> tempUpdateApiParams = new ArrayList<>(); // 临时保存，存在的需要更新的 ApiParam（虽然有 id 存在，但也有可能被其他人删除）
        // 批量保存 ApiParam
        List<ApiParam> apiParams = api.getApiParams();
        for (ApiParam apiParam : apiParams) {
            apiParam.setUpdatedAt(null);
            apiParam.setCreatedAt(null);

            // 生成示例数据
            if (api.getType() != CatalogType.INTERFACE_SSH && apiParam.getKind() != ParamKind.KIND_MOCK) {
                try {
                    JSONObject jsonSchema = new JSONObject(new JSONTokener(apiParam.getDes()));
                    Schema schema = SchemaLoader.load(jsonSchema);
                    GeneratorConfig generatorConfig = GeneratorConfig.fromJsonSchema(schema);
                    JsonGenerator jsonGenerator = new JsonGenerator(generatorConfig);
                    apiParam.setExample(jsonGenerator.generate());
                } catch (FileNotFoundException e) {
                }
            }

            // 按照 apiParam 没有 id 的，先将新增的和更新的分开
            if (apiParam.getId() == null || apiParam.getId() == 0) { // apiParam 没有 apiId，新增
                apiParam.setApiId(api.getId());
                newApiParams.add(apiParam);
            } else { // 更新
                tempUpdateApiParams.add(apiParam);
            }

            if (apiParam.getSort() == null) {
                apiParam.setSort(9999);
            }
            if (StringUtils.isEmpty(apiParam.getName())) {
                throw new MyException("更新失败，字段名值必填，请检查修改完成后重新保存");
            }
            if (api.getType() == CatalogType.INTERFACE_SSH) { // 如果是 ssh 类型的接口，需要把参数值保存到 paramMap 中，后续根据他来修改 step 中的内容
                paramMap.put(apiParam.getName(), apiParam.getExample());
            }
        }

        // 遍历 临时保存，存在的需要更新的 apiParam。如果存在，就放到 updateApiParams 中，更新。否则放到 newApiParams 中，新增
        List<Long> dataApiParamIds = apiParamDao.getListByApiId(api.getId()); // 根据 api id ，获得 apiParam 列表
        List<ApiParam> updateApiParams = new ArrayList<>();
        for (ApiParam apiParam : tempUpdateApiParams) {
            if (dataApiParamIds.remove(apiParam.getId())) {
                updateApiParams.add(apiParam);
            } else {
                newApiParams.add(apiParam);
            }
        }

        // 如果 dataApiParamIds 中还有，需要删除（可能是恢复历史记录时不存在，但恢复前存在）
        if (dataApiParamIds.size() > 0) {
            apiParamDao.deletePhysically(dataApiParamIds);
        }

        iApiParamService.saveBatch(newApiParams);
        iApiParamService.updateBatchById(updateApiParams);

        // 修改 ssh 接口，更新 step 中保存的 IP地址、端口号 等内容
        if (paramMap.size() > 0) {
            stepDao.updateExtra1ByApiId(api.getId(), JSON.toJSONString(paramMap));
        }

        iApiHistoryService.add(api, userName); // 添加历史记录

        return api;
    }

    @Override
    public int delete(Long id) {
        // 查找 api 是否被使用
        QueryWrapper<Step> stepQueryWrapper = new QueryWrapper<>();
        stepQueryWrapper.eq("api_id", id);
        List<Step> steps = stepDao.selectList(stepQueryWrapper);
        if (steps.size() != 0) {
            StringBuffer sb = new StringBuffer();
            for (Step step : steps) {
                sb.append(step.getTestCaseId());
                sb.append(" ");
            }
            throw new MyException("此 API 已被 「" + sb.toString() + "」 用例使用，禁止删除！");
        }

        // 删除 ApiParam
        QueryWrapper<ApiParam> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("api_id", id);
        apiParamDao.delete(queryWrapper);

        return apiDao.deleteById(id); // 删除 api
    }

    @Override
    public List<Api> search(SearchDTO searchDTO) {
        return apiDao.search(searchDTO.getProjectId(), searchDTO.getType(), searchDTO.getFindStr(), "%" + searchDTO.getFindStr() + "%");
    }

    @Async
    @Override
    @Transactional(timeout = 600)
    public Object apiFileImport(String taskId, FileType fileType, String filePath, Long projectId, String userName) {
        Map<String, List<Api>> apis = new HashMap<>();
        try {
            switch (fileType) {
                case OPEN_API:
                    apis = new OpenApiParser().toApi(filePath, projectId);
                    break;
                case POSTMAN:
                    apis = new PostmanParser().toApi(filePath, projectId);
                    break;
                default:
                    throw new MyException("导入接口定义错误，不支持的文件类型！");
            }
        } catch (Exception e) {
            iTaskManagerService.changeTaskStatus(taskId, TaskStatus.END_FAILED, e.getMessage());
            logger.error(e.toString());
        }

        // 插入 api
        try {
            for (Map.Entry<String, List<Api>> entry : apis.entrySet()) {
                String catalogName = entry.getKey();

                // 插入分类
                Long pId = 0L;
                if (StringUtils.isNotEmpty(catalogName)) {
                    for (String cName : catalogName.split("/")) { // 循环查找或创建分类
                        if (StringUtils.isNotEmpty(cName)) { // 非空才处理分类，避免这三种 bug "/aaa"、"aaa/"、"aaa//bbb"
                            Catalog temp = iCatalogService.selectByName(CatalogType.INTERFACE_HTTP, projectId, pId, cName);
                            if (temp == null) { // 不存在则插入，既然是 null 也就不需要在创建新变量了
                                temp = new Catalog();
                                temp.setProjectId(projectId);
                                temp.setType(CatalogType.INTERFACE_HTTP.getValue());
                                temp.setParentId(pId);
                                temp.setName(cName);
                                iCatalogService.save(temp);
                            }
                            pId = temp.getId();
                        }
                    }
                }

                // 插入 api
                for (Api api : entry.getValue()) {
                    api.setCatalogId(pId);
                    add(api, userName);
                }
            }
        } catch (Exception e) {
            iTaskManagerService.changeTaskStatus(taskId, TaskStatus.END_FAILED, "导入接口定义 - OpenAPI 格式，导入失败：插入 api 数据失败。" + e.getMessage());
            logger.error("导入接口定义 - OpenAPI 格式，导入失败。", e);
        }

        if (iTaskManagerService.getTask(taskId).getStatus().getValue() < TaskStatus.END_BREAK.getValue()) {
            iTaskManagerService.changeTaskStatus(taskId, TaskStatus.END_SUCCESS);
        } else if (iTaskManagerService.getTask(taskId).getStatus().equals(TaskStatus.END_FAILED)) {
            throw new RuntimeException("导入接口定义 - OpenAPI 格式，任务失败！");
        }
        return true;
    }

    @Override
    public Object apiDebug(ApiDebugDTO apiDebugDTO) {
        // 查询 agentKey
        String agentKey = "";
        if (apiDebugDTO.getAgentId() == 0) {
            Agent agent = iAgentService.getOnlineAgent();
            if (agent == null) {
                throw new MyException("没有在线的 agent，无法调试！");
            } else {
                agentKey = agent.getKey();
            }
        } else {
            Agent agent = agentDao.selectById(apiDebugDTO.getAgentId());
            if (agent.getStatus() == AgentStatus.OFF_LINE) {
                throw new MyException("您选取的「" + agent.getName() + "」agent 不在线，无法调试！");
            } else {
                agentKey = agent.getKey();
            }
        }

        // 设置 api url
        Api api = apiDebugDTO.getApi();
        if (StringUtils.isBlank(api.getHost())) {
            api.setUrl(api.getUri());
        } else {
            api.setUrl(api.getHost() + api.getUri());
        }

        // 通过接口定义中的内容，组装成 step
        Step step = new Step();
        step.setApi(api);
        step.setStepType(api.getType().getValue());
        step.setErrorHandlingType(ErrorHandlingType.STOP.getValue()); // 设置错误处理类型（由于是 debug 所以设置成 STOP-中断）
        step.setConditionType(ConditionType.NONE.getValue()); // 设置 条件类型（由于是 debug 所以设置成 NONE-不是条件步骤）
        for (ApiParam apiParam : api.getApiParams()) {
            if (api.getType() != CatalogType.INTERFACE_SSH && apiParam.getKind() != ParamKind.KIND_MOCK) {
                try {
                    JSONObject jsonSchema = new JSONObject(new JSONTokener(apiParam.getDes()));
                    Schema schema = SchemaLoader.load(jsonSchema);
                    GeneratorConfig generatorConfig = GeneratorConfig.fromJsonSchema(schema);
                    JsonGenerator jsonGenerator = new JsonGenerator(generatorConfig);
                    switch (apiParam.getKind()) {
                        case KIND_HEADER:
                            step.setExtra1(jsonGenerator.generate());
                            break;
                        case KIND_URL_PARAM:
                            step.setExtra2(jsonGenerator.generate());
                            break;
                        case KIND_BODY:
                            step.setExtra3(jsonGenerator.generate());
                            break;
                        case KIND_COOKIE:
                            step.setExtra4(jsonGenerator.generate());
                            break;
                        case KIND_ASSERT:
                            step.setExtra5("{}");
                            break;
                        default:
                            break;
                    }
                } catch (FileNotFoundException e) {
                }
            }
        }

        // 组装参数
        Map<String, String> globalParamMap = new HashMap<>(); // 全局参数（环境参数 + 静态参数 + 用户参数 + 套件唯一参数）
        List<GlobalParam> caseUniqUniquenessParams = new ArrayList<>(); // 用例唯一参数列表
        int uniqueValue = iTestSuiteService.getParamMap(apiDebugDTO.getProjectId(), apiDebugDTO.getEnv(), null, globalParamMap, caseUniqUniquenessParams); // 根据 项目id 和 环境，得到参数 map

        String sessionId = UUID.randomUUID().toString(); // 产成 sessionId，用作下发 debug 信令时作为唯一标识

        List<TestCaseDTO> testCasesDetailsList = new ArrayList<>(); // 待下发的所有测试用例

        TestCaseDTO testCaseDTO = new TestCaseDTO(); // 待下发的一个测试用例（由于是 debug 所以只有一个 testCase）
        testCaseDTO.setRid(0L); // debug 没有 testResultSuite，设置为 0
        if (api.getId() == null) { // 拿 cid 当 apiId 使用，所以对于没保存过的 api，id 设置成 0
            testCaseDTO.setCid(0L);
        } else {
            testCaseDTO.setCid(api.getId());
        }
        testCaseDTO.setName(api.getName() + "-debug");

        TestCase testCase = new TestCase();
        testCaseDTO.setDataProvider(testCase.getDataProvider());

        // 产生用例唯一参数，并设置全局变量
        for (GlobalParam globalParam : caseUniqUniquenessParams) {
            globalParamMap.put(globalParam.getParamName(), TimeUtil.generateTimestampParameter(globalParam.getLength(), uniqueValue++));
        }
        testCaseDTO.setGp((Map<String, String>) ((HashMap<String, String>) globalParamMap).clone());

        List<StepDTO> stepList = new ArrayList<>(); // 步骤列表，同样 debug 只有一个步骤
        StepDTO stepDTO = new StepDTO();
        stepDTO.setStep(step);
        stepList.add(stepDTO);

        testCaseDTO.setSteps(stepList);
        testCaseDTO.setSessionId(sessionId);

        testCasesDetailsList.add(testCaseDTO);
        logger.info("发送 Api 调试请求，待下发的用例列表。 testCasesDetailsList: {}: ", testCasesDetailsList);

        // 组装要下发的调试命令
        RunTestSuite runTestSuite = new RunTestSuite();
        runTestSuite.setMsg("debug");
        runTestSuite.setCaseType(CatalogType.INTERFACE_TEST_CASE.getValue());
        runTestSuite.setCases(testCasesDetailsList);

        // 组装公共步骤（debug 没有公共步骤直接放空）
        List<TestCaseDTO> pubTestCasesDetailsList = new ArrayList<>();
        runTestSuite.setPubCases(pubTestCasesDetailsList); // 将公共步骤，加入 TestSuite

        // 发送调试命令
        iAgentService.pubCmd(agentKey, "debug", JSON.toJSONString(runTestSuite));

        // 设置接口返回结果
        Map <String, Object> retMap = new HashMap<>();
        retMap.put("sessionId", sessionId);
        retMap.put("timestamp", System.currentTimeMillis());
        return retMap;
    }

    @Override
    public ApiLogDTO getApiDebugStatus(String sessionId, Long timestamp) {
        QueryWrapper<ApiLog> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("session_id", sessionId);

        List<ApiLog> apiLogs = apiLogDao.selectList(queryWrapper); // 查询结果
        if (apiLogs == null || apiLogs.size() == 0) {
            if (System.currentTimeMillis() - timestamp < 30000) { // 没查到结果，小于 30s 返回正在运行
                return new ApiLogDTO();
            } else { // 没查到结果，大于 30s 返回超时
                ApiLogDTO apiLogDTO = new ApiLogDTO();
                ApiLog apiLog = new ApiLog();
                apiLog.setSessionId(sessionId);
                apiLog.setStatus(RunStatus.ERROR.getValue());
                apiLog.setType("接口请求超时，请检查 agent 是否正常运行。");
                apiLogDTO.setResponse(apiLog);
                apiLogDTO.setResult(true);
                return apiLogDTO;
            }
        }

        ApiLogDTO apiLogDTO = new ApiLogDTO();
        apiLogDTO.setResult(true);
        for (ApiLog apiLog : apiLogs) {
            if (apiLog.getStatus() == RunStatus.INFO) {
                apiLogDTO.setResponse(apiLog);
            } else if (apiLog.getStatus() == RunStatus.PASSED || apiLog.getStatus().getValue() >= RunStatus.SKIPPED.getValue()) {
                apiLogDTO.setRequest(apiLog);
            }
        }
        return apiLogDTO;
    }

    @Override
    public Api apiCurlImport(CurlImportDTO curlImportDTO) {
        return new CurlParser().toApi(curlImportDTO);
    }

    @Override
    public String apiExport(int type, Long id) {
        Api api = apiDao.selectById(type, id);
        return new LocustExport().toLocust(api);
    }
}
